// Communicate with an IC-765 using the CI-V serial protocol via TCP/IP and a remote TCP to
// serial converter.  TCP merely carries the serial stream without any additional protocol.

#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int setup_connection(char *service, char *host) {
    int     unit;
    struct addrinfo hints, *res;

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;  // use IPv4 or IPv6, whichever
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;  // fill in my IP for me

    getaddrinfo(host, service, &hints, &res);

    if ((unit = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) < 0) {
        printf("socket() returned %d, %s\n", unit, strerror(errno));
        return 0;
    }
    if (connect(unit, res->ai_addr, res->ai_addrlen) < 0) {
        printf("connect() returned %d, %s\n", unit, strerror(errno));
        return 0;
    }
    return(unit);
}

int close_connection(int unit) {
    close(unit);
    return 0;
}

enum {
    STATE_IDLE = 0,
    STATE_FIRST_FE,
    STATE_SECOND_FE,
    STATE_TO_SEEN,
    STATE_FROM_SEEN,
    STATE_CMD_SEEN,
    STATE_DATA,
    STATE_DATA_SEEN,
};

struct Parser_State {
    int state;
    int command;
    int length;
    int to, from;
    int error;
    unsigned char buffer[20];
    int index;
    char digit[10];
};
        
int running;
long freq_sent;

void callback(long);

int decode_ci_v(struct Parser_State* state, unsigned char byte, void(*callback)()) {
    switch (state->state) {
    case STATE_IDLE:
        if (byte == 0xFE) {
            state->state=STATE_FIRST_FE;
            break;
        }
        break;
    case STATE_FIRST_FE:
        if (byte == 0xFE) {
            state->state=STATE_SECOND_FE;
            break;
        }
        break;
    case STATE_SECOND_FE:
        // printf("preamble\n");
        state->to = byte;
        state->state = STATE_TO_SEEN;
        break;
    case STATE_TO_SEEN:
        // printf("to = %d\n", state->to);
        state->from = byte;
        state->state = STATE_FROM_SEEN;
        break;
    case STATE_FROM_SEEN:
        // printf("from = %d\n", state->from);
        state->command = byte;
        state->state = STATE_CMD_SEEN;
        break;
    case STATE_CMD_SEEN:
        // printf("command = %d\n", state->command);
        switch (state->command) {
        case 03:
        case 05:
            if (state->from == 0xe0) {
                state->length = 0;
                state->state = STATE_DATA_SEEN;
                goto STATE_DATA_SEEN;
            } else {
                state->length = 5;
                // printf("gonna get freq\n");
            }
            break;
        case 04:
            if (state->from == 0xe0) {
                state->length = 0;
                state->state = STATE_DATA_SEEN;
                goto STATE_DATA_SEEN;
            } else {
                state->length = 2;
                // printf("gonna get mode\n");
            }
            break;
        default:
            state->length = 1;
            break;
        }
        state->index = 0;
        state->buffer[state->index] = byte;
        state->index += 1;
        state->length -= 1;
        state->state = STATE_DATA;
        break;
    case STATE_DATA:
        // printf("data = %d\n", state->buffer[state->index]);
        if (state->index <= sizeof(state->buffer)) {
            state->buffer[state->index] = byte;
        }
        state->index += 1;
        state->length -= 1;
        if (state->length <= 0) {
            state->state = STATE_DATA_SEEN;
            switch (state->command) {
            case 03:
            case 05:
                state->digit[8] = '0' + (state->buffer[0] >> 4);
                state->digit[9] = '0' + (state->buffer[0] & 0x0f);
                state->digit[6] = '0' + (state->buffer[1] >> 4);
                state->digit[7] = '0' + (state->buffer[1] & 0x0f);
                state->digit[4] = '0' + (state->buffer[2] >> 4);
                state->digit[5] = '0' + (state->buffer[2] & 0x0f);
                state->digit[2] = '0' + (state->buffer[3] >> 4);
                state->digit[3] = '0' + (state->buffer[3] & 0x0f);
                state->digit[0] = '0' + (state->buffer[4] >> 4);
                state->digit[1] = '0' + (state->buffer[4] & 0x0f);
                state->digit[10] = 0;
                // printf("freq = %s\n", state->digit);
                callback(strtol(state->digit, 0, 10));
                break;
            default:
                break;
            }
        }
        break;
    STATE_DATA_SEEN:
    case STATE_DATA_SEEN:
        if (byte == 0xfd) {
            state->state = STATE_IDLE;
            state->error = 0;
        } else {
            state->state = STATE_IDLE;
            state->error = 1;
        }
    }
    return 0;
}

int SetRemoteRadioFrequency(long frequency) {
    int index, bytes, byte_count = 0;
    struct Parser_State state = {0};
    int fd;
    unsigned char buffer[100];
    unsigned char query[] = {0xfe, 0xfe, 0x2c, 0xe0, 0x05, 0x78, 0x56, 0x34, 0x12, 0x00, 0xfd};

    sprintf((char *)buffer, "%010ld", frequency);
    query[9] = (buffer[1] - '0') | ((buffer[0] - '0') << 4);
    query[8] = (buffer[3] - '0') | ((buffer[2] - '0') << 4);
    query[7] = (buffer[5] - '0') | ((buffer[4] - '0') << 4);
    query[6] = (buffer[7] - '0') | ((buffer[6] - '0') << 4);
    query[5] = (buffer[9] - '0') | ((buffer[8] - '0') << 4);
    printf("Setting radio to %s\n", buffer);
    if((fd = setup_connection("4532", "192.168.2.103")) == 0) {
        return 0;
    }
    send(fd, query, sizeof(query), 0);
    running = 1;
    while (running) {
        bytes = recv(fd, buffer, 1, 0);
        if (bytes <= 0) {
            break;
        }
        for (index = 0; index < bytes; index++) {
            // printf("%02x ", buffer[index]);
            byte_count += 1;
            if (byte_count % 8 == 0) {
                // printf("\n");
            }
            decode_ci_v(&state, buffer[index], callback);
        }
    }
    close_connection(fd);
    printf("finished after %d bytes\n", byte_count);
    return 0;
}

#ifdef TEST
int main(int argc, char *argv[]) {
    printf("remote CI-V test\n");
    freq_sent = atoi(argv[1]);
    SetRemoteRadioFrequency(freq_sent);
    return 0;
}

void callback(long freq_read) {
    if (freq_read == freq_sent) {
        running = 0;
    }
}
#endif
